/*
 * sgf.js
 * http://dev.akinao.com/
 */
//@require name.js,lang.js
akinao.namespace('game');
(function(){

	var Lang = akinao.lang;

	function SgfParseException(message) {
		this.name = 'SgfParseException';
		this.message = message;
		this.description = this.name + ': message ' + message;
	}
	SgfParseException.prototype = new Error;

	function SgfVariationsException(message) {
		this.name = 'SgfVariationsException';
		this.message = message;
		this.description = this.name + ': message ' + message;
	}
	SgfVariationsException.prototype = new Error;

	function Tokenizer(v) {
		this.val = v;
		this.len = v.length;
		this.tg = 0;
		this.ch = '';
	}

	Tokenizer.prototype = {
		get:function() {
			return this.ch;
		},
		index:function() {
			return this.tg;
		},
		isSkip:function() {
			return -1!=" \t\n\r\\".indexOf(this.ch);
		},
		isValueStart:function() {
			return '[' == this.ch;
		},
		isValueEnd:function() {
			return ']' == this.ch
		},
		isNodeStart:function() {
			return ';' == this.ch
		},
		isNodeDemiliter:function() {
			return -1!=';()'.indexOf(this.ch);
		},
		isArrayStart:function() {
			return '(' == this.ch;
		},
		isArrayEnd:function() {
			return ')' == this.ch;
		},
		next:function() {
			if (this.tg < this.len) {
				this.ch = this.val.charAt(this.tg++);
				return true;
			} else {
				return false;
			}
		}
	};

/*
	// is there another function?
	function isNotEmpty(o) {
		for (var i in o) {return true;}
		return false;
	}
*/
	function tokenValues(tk) {
		var vs = [],v = '',s = true;
		do{
			if (tk.isValueStart()) {
				v = '';
				s = false;
			} else if (tk.isValueEnd()) {
				vs.push(v);
				s = true;
			} else {
				if (s&&!tk.isSkip()) {
					if (1 == vs.length) {
						vs = vs[0];
					}
					return vs;
				}
				v += tk.get();
			}
		} while (tk.next());
		throw new SgfParseException("Missing end.");
	}

	function tokenProperty(tk,p) {
		var n = '';
		do {
			if (tk.isValueStart()) {
				p[n] = tokenValues(tk);
				return p;
			} else {
				if (tk.isSkip()) {
					if (0 < n.length) {
						throw new SgfParseException("property name is invalid.");
					}
				} else {
					n += tk.get();
				}
			}
		} while (tk.next());
		throw new SgfParseException("Missing end.");
	}

	function tokenNode(tk) {
		var p = {};
		// skip ';'
		if (tk.isNodeStart() && !tk.next()) {
			throw new SgfParseException("Missing end.");
		}
		while (true) {
			if (tk.isNodeDemiliter()) {
				return p;
			} else if (tk.isSkip()) {
				if (!tk.next()) {
					throw new SgfParseException("Missing end.");
				}
			} else {
				var p = tokenProperty(tk,p);
			}
		}
	}

	function tokenTree(tk) {
		if (!tk.next()) {
			throw new SgfParseException("Missing end.");
		}
		var tr = [],sb = [];
		while(true) {
			if (tk.isArrayStart()) {
				var s = tokenTree(tk);
				// escape empty tree.
//				var f=false;
//				for (var i in s) {
//					if (isNotEmpty(s[i])) {
//						f=true;
//						break;
//					}
//				}
//				if (f) {sb.push(s)};
				sb.push(s);
				if (!tk.next()) {
					throw new SgfParseException("Missing end.");
				}
			} else if (tk.isArrayEnd()) {
				if (0 < sb.length) {tr.push(sb);}
				return tr;
			} else if (tk.isSkip()) {
				if (!tk.next()) {
					throw new SgfParseException("Missing end.");
				}
			} else {
				// escape empty node.
//				var n=tokenNode(tk);
//				if (isNotEmpty(n)) {tr.push(n);}
				tr.push(tokenNode(tk));
			}
		}
	}

	function parseSgf(s) {
		if (!s || false == Lang.isString(s) || 0 == jQuery.trim(s).length) {
			return [{}];
		}
		var tk = new Tokenizer(s);
		// skip to first '('
		while(true) {
			if (!tk.next()) {
				throw new SgfParseException("Missing end.");
			}
			if (tk.isArrayStart()) {break;}
		}
		var r=tokenTree(tk);
		if (!r[0]) {
			r = [{}];
		}
		return r;
	}

	function Sgf(s) {
		if (Lang.isUndefined(s)) {
			this.sgf = [{}];
		} else {
			this.sgf = parseSgf(s);
		}
		this.tgAry = this.sgf;
		this.tgIdx = 0;
		this.tgTree = [];
		this.count = 0;
		while (this.next()) {
			this.count++;
		}
		this.first();
	}

	Sgf.prototype.get = function(n) {
		var r = this.tgAry[this.tgIdx];
//		if (n&&!r.length) {
//			throw new SgfVariationsException('Index out of bounds.');
//		}
		// Variations
		if (r.length) {
			if (n) {
//				if (0>n||n>=r.length) {
//					throw new SgfVariationsException('Index out of bounds.');
//				}
				r = r[n];
			} else {
				// [[1,2,3],[4,5,6],[7,8,9]]
				var a = [];
				for (var i in r) {
					a.push(r[i][0]);
				}
				// [1,4,7]
				r = a;
			}
		}
		return r;
	};

	Sgf.prototype.info = function() {
		return this.sgf[0];
	};

	Sgf.prototype.next = function(n) {
		// Variations
		//[1,2,3,[[4,5,6],[7,8,9]]]
		if (this.tgAry[this.tgIdx].length) {
			var a = this.tgAry[this.tgIdx];//[[4,5,6],[7,8,9]]
			if (Lang.isUndefined(n)) {
				n = 0;
			}
//			if (0 > n || n >= a.length) {
//				throw new SgfVariationsException('Index out of bounds.');
//			}
			var b = a[n];//[4,5,6]
			if (2 > b.length) {
				return false;
			}
			this.tgTree.push(this.tgAry);
			this.tgAry = b;
			this.tgIdx = 1;
			return true;
		// Single
		} else if (this.tgIdx < this.tgAry.length - 1) {
			this.tgIdx++;
			return true;
		}
		// Tail
		return false;
	};

	Sgf.prototype.previous = function() {
		if (0 == this.tgIdx) {
			return false;
		} else {
			this.tgIdx--;
			if (0 == this.tgIdx && 0 < this.tgTree.length) {
				this.tgAry = this.tgTree.pop();
				this.tgIdx = this.tgAry.length - 1;
			}
			return true;
		}
	};
	Sgf.prototype.first =  function() {
		while (this.previous()) {};
	};

	// o:value
	// n:if target is variations, append on n.
	Sgf.prototype.append = function(o, n) {
		var v = {};
		for (var i in o) {
			v[i] = o[i].toString();
		}
		// Append Variations
		if (this.tgAry[this.tgIdx].length) {
			// tgAry=[1,2,[[3,4,5],[6,7,8]]] tgIdx=3 n=2 v=9
			if (Lang.isUndefined(n)) {
				// tgAry=[1,2,[[3,4,5],[6,7,8]]] tgIdx=3 v=9
				// tgAry=[1,2,[[3,4,5],[6,7,8],[9]]]
				this.tgAry[this.tgIdx].push([v]);
			} else {
				// a=[[3,4,5],[6,7,8]]
				var a = this.tgAry[this.tgIdx];
//				if (0>n||n>=a.length) {
//					throw new SgfVariationsException('Index out of bounds.');
//				}
				// [[3,4,5],[6,7,8,9]]
				a[n].push(v);
				this.tgTree.push(this.tgAry);
				this.tgAry = a[n];
				this.tgIdx = 1;
			}
		// Tail
		} else if (this.tgIdx == this.tgAry.length - 1) {
			this.tgAry.push(v);
			this.tgIdx++;
		// Create Variations
		} else {
			// tgAry=[1,2,3,4,5] tgIdx=2
			var o = this.tgAry.slice(this.tgIdx + 1);
			// o=[4,5]
			var a = [o,[v]];
			// a=[[4,5],[v]]
			var i = this.tgAry.length - 1;
			// this.tgAry=[1,2,3]
			while (this.tgIdx < i--) {
				this.tgAry.pop();
			}
			// this.tgAry=[1,2,3,[[4,5],[v]]]
			this.tgAry.push(a);
			// tgIdx=3
			this.tgIdx++;
		}
	};

	Sgf.prototype.remove = function(n) {
		if (this.tgAry[this.tgIdx].length) {
			// tgAry=[1,2,[[3,4],[6,7],[8,9]]] tgIdx=2 n=1
			if (Lang.isUndefined(n)) {
				n = 0;
			}
			// a=[[3,4],[6,7],[8,9]]
			var a = this.tgAry[this.tgIdx];
//			if (n>0||n>=a.length) {
//				throw new SgfVariationsException('Index out of bounds.');
//			}
			var r = [];
			if (0 < n) {
				r.push(a.slice(0, n)[0]);
			}
			if (n < a.length - 1) {
				r.push(a.slice(n + 1)[0]);
			}
//			r=r[0];
//			document.write("OK"+r.toSource());
			if (1 == r.length) {
				this.tgAry.pop();
				for (var i in r[0]) {
					this.tgAry.push(r[0][i]);
				}
			} else {
				this.tgAry[this.tgIdx] = r;
			}
		} else if (0 < this.tgIdx) {
			var i = this.tgAry.length;
			// this.tgAry=[1,2,3]
			while(this.tgIdx < i--) {
				this.tgAry.pop();
			}
			this.tgIdx--;
			if (0 == this.tgIdx && 0 < this.tgTree.length) {
				this.tgAry = this.tgTree.pop();
				this.tgIdx = this.tgAry.length-1;
			}
		}
	};

	Sgf.prototype.toString = function() {
		function array(a, f) {
			var r = '';
			for (var i in a) {
				var o = a[i];
				if (o.length) {
					r += (f ? '(' : '') + array(o,!f) + (f ? ')' : '');
				} else {
					r += ';' + node(o);
				}
			}
			return r;
		}
		//TODO escape []();
		function node(o) {
			var r = '';
			for (var p in o) {
				r += p;
				if (typeof o[p] === "string") {
					r += '[' + o[p] + ']';
				} else {
					for (var i in o[p]) {
						r += '[' + o[p][i] + ']';
					}
				}
			}
			return r;
		}
		return array([this.sgf],true);
	};

	akinao.game.Sgf = Sgf;
})();
